from __future__ import annotations
from typing import List, Optional, Union

from axiom_boot.conf.manager import Settings
from axiom_boot.di import autowired, service
from axiom_boot.di.application_context import ApplicationContext
from axiom_boot.logging.setup import get_logger
from axiom_boot.scraper.exceptions import DropItem, RequestAborted
from axiom_boot.scraper.interfaces import ItemPipeline, Extractor, ScraperMiddleware
from axiom_boot.scraper.models import Target, Response, Item


logger = get_logger(__name__)


@service()
class ScraperEngine:
    _context: ApplicationContext = autowired()
    _settings: Settings = autowired()

    def __init__(self):
        self._middlewares: Optional[List[ScraperMiddleware]] = None
        self._pipelines: Optional[List[ItemPipeline]] = None

    async def scrape(self, target: Target) -> List[Item]:
        try:
            # 1. 应用请求中间件
            req_mw_result = await self._apply_request_middlewares(target)

            if req_mw_result is None:
                raise RequestAborted("请求被中间件中止。")

            if isinstance(req_mw_result, Response):
                response = req_mw_result  # 中间件直接返回了响应，跳过下载
            else:
                target = req_mw_result  # 否则，返回的是处理过的 Target
                # 2. 核心下载逻辑
                response = await self._download(target)

            # 3. 应用响应中间件
            resp_mw_result = await self._apply_response_middlewares(target, response)

            if resp_mw_result is None:
                raise RequestAborted("响应被中间件丢弃。")

            if isinstance(resp_mw_result, Target):
                # 中间件要求重新调度
                logger.debug(f"中间件请求为 {target.url} 重新调度新的 Target: {resp_mw_result.url}")
                return await self.scrape(resp_mw_result)

            response = resp_mw_result

            # 4. 提取 Items
            extractor = self._get_extractor(target)
            if not extractor:
                return []
            items = extractor.extract(response, target)

            # 5. Item Pipeline 处理
            return await self._process_items_through_pipeline(items, response)

        except Exception as e:
            # 应用异常处理中间件
            exc_mw_result = await self._apply_exception_middlewares(target, e)

            if isinstance(exc_mw_result, Target):
                logger.debug(f"异常被中间件处理，将为 {target.url} 重新调度新的 Target: {exc_mw_result.url}")
                return await self.scrape(exc_mw_result)

            if isinstance(exc_mw_result, Response):
                logger.debug(f"异常被中间件处理，将使用新的 Response 继续处理流程。")
                extractor = self._get_extractor(target)
                if not extractor:
                    return []
                items = extractor.extract(exc_mw_result, target)
                return await self._process_items_through_pipeline(items, exc_mw_result)
            
            if isinstance(e, RequestAborted):
                logger.info(f"请求处理中止: {target.url}。原因: {e}")
                return []

            # 如果异常未被处理，则向上传播
            raise

    async def _download(self, target: Target) -> Response:
        downloader_name = target.downloader or self._settings.scraper.default_downloader
        downloader = await self._context.get(downloader_name)
        return await downloader.download(target)

    async def _apply_request_middlewares(self, target: Target) -> Optional[Union[Target, Response]]:
        for mw in self._get_middlewares():
            result = await mw.process_request(target)
            if result is None:
                return None
            if isinstance(result, Response):
                return result
            target = result
        return target

    async def _apply_response_middlewares(self, target: Target, response: Response) -> Optional[Union[Target, Response]]:
        for mw in self._get_middlewares():
            result = await mw.process_response(target, response)
            if result is None:
                return None
            if isinstance(result, Target):
                return result
            response = result
        return response

    async def _apply_exception_middlewares(self, target: Target, exception: Exception) -> Optional[Union[Target, Response]]:
        for mw in reversed(self._get_middlewares()):
            result = await mw.process_exception(target, exception)
            if result is not None:
                return result
        return None

    def _get_extractor(self, target: Target) -> Optional[Extractor]:
        """从 Target 中获取提取器实例。"""
        extractor = target.extractor
        if not extractor:
            logger.warning(f"URL {target.url} 的爬取任务未指定提取器 (Extractor)。")
            return None
        if not isinstance(extractor, Extractor):
            raise TypeError(f"目标中提供的 'extractor' 必须是 Extractor 的一个实例，但收到了 {type(extractor)}。")
        return extractor

    async def _process_items_through_pipeline(
        self, items: List[Item], response: Response
    ) -> List[Item]:
        if not items:
            return []

        processed_items = []
        pipelines = self._get_pipelines()
        for item in items:
            item_being_processed = item
            try:
                for pipeline in pipelines:
                    processed_result = pipeline.process_item(item_being_processed, response)
                    if processed_result is None:
                        raise DropItem(f"Item 在 {pipeline.__class__.__name__} 中被丢弃。")
                    item_being_processed = processed_result
                processed_items.append(item_being_processed)
            except DropItem as e:
                logger.debug(str(e))
                continue
        
        logger.info(f"经过 {len(pipelines)} 个管道处理后，剩余 {len(processed_items)} 个条目。")
        return processed_items
    
    def _get_middlewares(self) -> List[ScraperMiddleware]:
        if self._middlewares is None:
            self._middlewares = self._context.get_instances_by_superclass(ScraperMiddleware)
            # [关键修复] 使用正确的属性名 '_axiom_order' 进行排序
            self._middlewares.sort(key=lambda m: getattr(m, '_axiom_order', float('inf')))
        return self._middlewares

    def _get_pipelines(self) -> List[ItemPipeline]:
        if self._pipelines is None:
            self._pipelines = self._context.get_instances_by_superclass(ItemPipeline)
            # [关键修复] 使用正确的属性名 '_axiom_order' 进行排序
            self._pipelines.sort(key=lambda p: getattr(p, '_axiom_order', float('inf')))
        return self._pipelines 